// PjLibDLL.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include <iostream>
#include <stdio.h>
using std::cerr;
using std::endl;
#include <fstream>
using std::ofstream;
#include <windows.h>

#include "pjmedia.h"
#include "pjmedia-codec.h"
#include "pjlib-util.h"	
#include "pjlib.h"
#include "pjmedia/transport_srtp.h"

// Attiva sicurezza srtp
#define USA_SRTP 1
#define PJ_WIN32_WINCE 1

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}

// AVVIA PJMEDIA
int start(wchar_t* ip, wchar_t* port, wchar_t* codec, wchar_t* key_TX, wchar_t* key_RX);
/*
Ritorna:
0 Tutto ok;
1 ERRORE! Inizializzazione
2 ERRORE! Indirizzo ip o porta non valida
3 ERRORE! Impossibile trovare il codec

*/

// TERMINA PJMEDIA
int stop();
/*
Da chiamare ogni per terminare pjmedia 
e ogni volta che start() da errore per pulire
*/

#ifdef USA_SRTP

inline int hex_char_to_nibble(char c) {
  switch(c) {
  case ('0'): return 0x0;
  case ('1'): return 0x1;
  case ('2'): return 0x2;
  case ('3'): return 0x3;
  case ('4'): return 0x4;
  case ('5'): return 0x5;
  case ('6'): return 0x6;
  case ('7'): return 0x7;
  case ('8'): return 0x8;
  case ('9'): return 0x9;
  case ('a'): return 0xa;
  case ('A'): return 0xa;
  case ('b'): return 0xb;
  case ('B'): return 0xb;
  case ('c'): return 0xc;
  case ('C'): return 0xc;
  case ('d'): return 0xd;
  case ('D'): return 0xd;
  case ('e'): return 0xe;
  case ('E'): return 0xe;
  case ('f'): return 0xf;
  case ('F'): return 0xf;
  default: break;   /* this flags an error */
  }
  return -1;
}

int hex_string_to_octet_string(char *raw, char *hex, int len) {
  char x;
  int tmp;
  int hex_len;

  hex_len = 0;
  while (hex_len < len) {
    tmp = hex_char_to_nibble(hex[0]);
    if (tmp == -1)
      return hex_len;
    x = (tmp << 4);
    hex_len++;
    tmp = hex_char_to_nibble(hex[1]);
    if (tmp == -1)
      return hex_len;
    x |= (tmp & 0xff);
    hex_len++;
    *raw++ = x;
    hex += 2;
  }
  return hex_len;
}

#endif

// Variabili Globali
pj_caching_pool *cp = NULL;
pjmedia_endpt *med_endpt = NULL;
pj_pool_t *pool = NULL;
pjmedia_stream *stream = NULL;
pjmedia_snd_port *snd_port = NULL;
pjmedia_stream_info *info = NULL;
pjmedia_codec_param *codec_param = NULL;
pjmedia_port *stream_port= NULL;
pjmedia_transport *transport = NULL;

int start(wchar_t* ipad2, wchar_t* porta2, wchar_t* codec2, wchar_t* key_TX2, wchar_t* key_RX2){
	
	// Ricavo i parametri da formato Unicode ad ANSI
	char* ipad = new char[wcslen(ipad2)];
	wcstombs( ipad, ipad2, wcslen(ipad2) );
	char* porta = new char[wcslen(porta2)];
	wcstombs( porta, porta2, wcslen(porta2) );
	char* codec = new char[wcslen(codec2)];
	wcstombs( codec, codec2, wcslen(codec2) );
	char* key_TX = new char[wcslen(key_TX2)];
	wcstombs( key_TX, key_TX2, wcslen(key_TX2) );
	char* key_RX = new char[wcslen(key_RX2)];
	wcstombs( key_RX, key_RX2, wcslen(key_RX2) );

	pj_str_t ip = pj_str(ipad);
	pj_uint16_t port = (pj_uint16_t) atoi(porta);
	pj_str_t codec_id = pj_str(codec);

	// Definizioni
	pj_status_t status; 
	pj_uint16_t local_port;
	pjmedia_dir dir = PJMEDIA_DIR_ENCODING_DECODING;
	pj_sockaddr_in remote_addr;
	pj_bzero(&remote_addr, sizeof(remote_addr));
	const pjmedia_codec_info *codec_info=NULL;
	cp = new pj_caching_pool();
	info = new pjmedia_stream_info();
	pj_bzero(info, sizeof(pjmedia_stream_info));
	codec_param = new pjmedia_codec_param();

#ifdef USA_SRTP

	/* SRTP variables */
    pj_bool_t use_srtp = PJ_TRUE;
    char tmp_tx_key[64];
    char tmp_rx_key[64];
    pj_str_t  srtp_tx_key = {NULL, 0};
    pj_str_t  srtp_rx_key = {NULL, 0};
    pj_str_t  srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
    int	tmp_key_len;

	tmp_key_len = hex_string_to_octet_string(tmp_tx_key, key_TX, strlen(key_TX));
	pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);

	tmp_key_len = hex_string_to_octet_string(tmp_rx_key, key_RX, strlen(key_RX));
	pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);

	pjmedia_transport *srtp_tp = NULL;

#endif


	// Init PJLIB
    status = pj_init();
	if (status != PJ_SUCCESS){
		//printf("ERRORE! pj_init()\n");
		return 1;
	}

	// Controllo ip
	status = pj_sockaddr_in_init(&remote_addr, &ip, port);
	if (status != PJ_SUCCESS) {
	    //printf("ERRORE! Indirizzo ip o porta non valida\n");
		return 2;
	}
	local_port=port;

	/* Must create a pool factory before we can allocate any memory. */
    pj_caching_pool_init(cp, &pj_pool_factory_default_policy, 0);

    /* Initialize media endpoint. */
    status = pjmedia_endpt_create(&cp->factory, NULL, 1, &med_endpt);
    if (status != PJ_SUCCESS){
		//printf("ERRORE! 1\n");
		return 3;
	}

    /* Create memory pool for application purpose */
    pool = pj_pool_create( &cp->factory,"app",4000,4000,NULL);

	/* Register all supported codecs */
#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
    status = pjmedia_codec_g711_init(med_endpt);
    if (status != PJ_SUCCESS){
		//printf("ERRORE! 2\n");
		return 4;
	}
#endif

#if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC!=0
    status = pjmedia_codec_gsm_init(med_endpt);
    if (status != PJ_SUCCESS){
		//printf("ERRORE! 3\n");
		return 5;
	}
#endif

#if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
    status = pjmedia_codec_speex_init(med_endpt, 0, -1, -1);
    if (status != PJ_SUCCESS){
		//printf("ERRORE! 4\n");
		return 6;
	}
#endif

#if defined(PJMEDIA_HAS_G722_CODEC) && PJMEDIA_HAS_G722_CODEC!=0
    status = pjmedia_codec_g722_init(med_endpt);
    if (status != PJ_SUCCESS){
		//printf("ERRORE! 5\n");
		return 7;
	}
#endif

#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
    status = pjmedia_codec_l16_init(med_endpt, 0);
    if (status != PJ_SUCCESS){
		//printf("ERRORE! 6\n");
		return 8;
	}
#endif

	 /* Find which codec to use. */
	if (codec) {
		unsigned count = 1;
		pj_str_t str_codec_id = pj_str(codec);
		pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
		status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,&str_codec_id, &count,&codec_info, NULL);
		if (status != PJ_SUCCESS) {
			//printf("ERRORE! Impossibile trovare il codec %s\n", codec_id);
			return 9;
		}
    } else {
		/* Default to pcmu */
		pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),0, &codec_info);
    }

	/* Reset stream info. */
    pj_bzero(info, sizeof(pjmedia_stream_info));

	/* Initialize stream info formats */
    info->type = PJMEDIA_TYPE_AUDIO;
    info->dir = dir;
    pj_memcpy(&info->fmt, codec_info, sizeof(pjmedia_codec_info));
    info->tx_pt = codec_info->pt;
    info->ssrc = pj_rand();

	/* Copy remote address */
    pj_memcpy(&info->rem_addr, &remote_addr, sizeof(pj_sockaddr_in));

	/* Create media transport */
    status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,0, &transport);
	if (status != PJ_SUCCESS){
		//printf("ERRORE! 7\n");
		return 10;
	}

#ifdef USA_SRTP
    /* Check if SRTP enabled */
    if (use_srtp) {
	pjmedia_srtp_crypto tx_plc, rx_plc;

	status = pjmedia_transport_srtp_create(med_endpt, transport,NULL, &srtp_tp);
	if (status != PJ_SUCCESS){
		//printf("ERRORE! 8\n");
		return 11;
	}

	pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
	pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));

	tx_plc.key = srtp_tx_key;
	tx_plc.name = srtp_crypto_suite;
	rx_plc.key = srtp_rx_key;
	rx_plc.name = srtp_crypto_suite;
	
	status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
	if (status != PJ_SUCCESS){
		//printf("ERRORE! 9\n");
		return 12;
	}

	transport = srtp_tp;
    }
#endif

	status = pjmedia_stream_create( med_endpt, pool, info,transport,NULL,&stream);
    if (status != PJ_SUCCESS) {
		//printf("ERRORE! Creazione stream fallita\n");
		pjmedia_transport_close(transport);
		return 13;
    }

	/* Get codec default param for info */
    status = pjmedia_codec_mgr_get_default_param(pjmedia_endpt_get_codec_mgr(med_endpt),codec_info,codec_param);
	if (status != PJ_SUCCESS){
		//printf("ERRORE! 10\n");
		return 14;
	}

	/* Get the port interface of the stream */
    status = pjmedia_stream_get_port( stream, &stream_port);
    if (status != PJ_SUCCESS){
		//printf("ERRORE! 11\n");
		return 15;
	}

	/* Create sound device port. */
	status = pjmedia_snd_port_create(pool, -1, -1,stream_port->info.clock_rate,stream_port->info.channel_count,
									stream_port->info.samples_per_frame,stream_port->info.bits_per_sample,0, &snd_port);
	if (status != PJ_SUCCESS) {
	    //printf("ERRORE! Impossibile creare sound port");
	    return 16;
	}

	pjmedia_port *  	dn_port;
	pjmedia_echo_port_create(pool,stream_port,200,0,PJMEDIA_ECHO_SIMPLE,&dn_port);	 	

	/* Connect sound port to stream */
	//status = pjmedia_snd_port_connect( snd_port, stream_port );
	status = pjmedia_snd_port_connect( snd_port, dn_port );
	if (status != PJ_SUCCESS){
		//printf("ERRORE! 12\n");
		return 17;
	}

    /* Start streaming */
    pjmedia_stream_start(stream);


    /* Done */
	//printf("Streaming ATTIVO!!!\n");
	
	/*FILE *myfile;
	myfile = fopen("parameters.txt", "w");
	fprintf(myfile, "\nPhone.exe\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d",&cp, &med_endpt, &pool, &stream, &snd_port, &info, &codec_param, &stream_port, &transport);
	fclose(myfile);*/
	
	//Deallocare qui i parametri
	/*delete ipad;
	delete porta;
	delete codec;
	delete key_TX;
	delete key_RX;*/

	return 0;
}

int stop(){

	/*FILE *myfile;
	myfile = fopen("parameters2.txt", "w");
	fprintf(myfile, "\nPhone.exe\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d",&cp, &med_endpt, &pool, &stream, &snd_port, &info, &codec_param, &stream_port, &transport);
	fclose(myfile);*/

	//Destroy sound device 
    if (snd_port!=NULL) {
		pjmedia_snd_port_destroy( snd_port );
    }

    /* Destroy stream */
    if (stream!=NULL) {
		pjmedia_transport *tp;
		tp = pjmedia_stream_get_transport(stream);
		pjmedia_stream_destroy(stream);	
		pjmedia_transport_close(tp);
    }

#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
    pjmedia_codec_g711_deinit();
#endif

#if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC!=0
    pjmedia_codec_gsm_deinit();
#endif

#if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
    pjmedia_codec_speex_deinit();
#endif

#if defined(PJMEDIA_HAS_G722_CODEC) && PJMEDIA_HAS_G722_CODEC!=0
    pjmedia_codec_g722_deinit();
#endif

#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
    pjmedia_codec_l16_deinit();
#endif

    /* Release application pool */
	if(pool!=NULL)
		pj_pool_release( pool );

    /* Destroy media endpoint. */
	if(med_endpt!=NULL)
		pjmedia_endpt_destroy( med_endpt );

    /* Destroy pool factory */
	if(cp!=NULL)
		pj_caching_pool_destroy( cp );

    /* Shutdown PJLIB */
    pj_shutdown();

	
	/* Cancello e pulisco */
	delete med_endpt;
	delete pool;
	delete stream;
	delete snd_port;
	delete info;
	delete codec_param;
	delete stream_port;
	delete transport;
	cp = NULL;
	med_endpt = NULL;
	pool = NULL;
	stream = NULL;
	snd_port = NULL;
	info = NULL;
	codec_param = NULL;
	stream_port= NULL;
	transport = NULL;

	//printf("PJMedia Terminata!!!\n");
	return 0;
}

static const char *good_number(char *buf, pj_int32_t val)
{
    if (val < 1000) {
	pj_ansi_sprintf(buf, "%d", val);
    } else if (val < 1000000) {
	pj_ansi_sprintf(buf, "%d.%dK", 
			val / 1000,
			(val % 1000) / 100);
    } else {
	pj_ansi_sprintf(buf, "%d.%02dM", 
			val / 1000000,
			(val % 1000000) / 10000);
    }

    return buf;
}


#define SAMPLES_TO_USEC(usec, samples, clock_rate) \
    do { \
	if (samples <= 4294) \
	    usec = samples * 1000000 / clock_rate; \
	else { \
	    usec = samples * 1000 / clock_rate; \
	    usec *= 1000; \
	} \
    } while(0)

#define PRINT_VOIP_MTC_VAL(s, v) \
    if (v == 127) \
	sprintf(s, "(na)"); \
    else \
	sprintf(s, "%d", v)